import IO, { type Socket } from "socket.io-client";
import { isEmpty, isFunction } from "lodash-es";
import { useUserStore } from "@/store/modules/user";
import { SocketStatus } from "@/enums";
import $bus from "@/utils/bus";

export class SocketIOWrapper {
  /**
   * socket.io-client reserved event keywords
   * @type {string[]}
   */
  static staticEvents = [
    // socket instance listen
    "connectok",
    "connect_error",
    "disconnect",
    "disconnecting",
    "newListener",
    "removeListener",
    // Manager listen
    "error",
    "reconnect",
    "reconnect_attempt",
    "reconnect_error",
    "reconnect_failed"
  ] as const;
  socketInstance: Socket | null;
  emitQueue: any[];
  closeByServer: boolean;
  waiting: boolean;
  flushing: boolean;
  handleIndex: number;
  runningQueue: any[];
  socketStore: any;
  storeName: string;

  constructor(store: any, name: string) {
    // socket io client instance
    this.socketStore = store;
    this.storeName = name;
    this.socketInstance = null;
    // emit cache queue
    this.emitQueue = [];
    // flush will using
    this.runningQueue = [];
    this.handleIndex = 0;
    this.flushing = false;
    this.waiting = false;
    // is server disconnect
    this.closeByServer = false;
    // init
    this._init();
  }

  /**
   * 获取当前连接的clientid
   */
  getClientId() {
    if (this.socketInstance) {
      return this.socketInstance.id;
    }
    return undefined;
  }

  /**
   * 获取真实的Socket连接状态
   */
  isConnected() {
    return this.socketInstance?.connected;
  }

  // 主动关闭连接
  close() {
    if (this.socketInstance) {
      this.socketInstance.close();
    }
    this.socketInstance = null;
  }

  // SocketIO 初始化
  _init() {
    if (this.socketInstance && this.socketInstance.connected) {
      throw new Error("socket is connecting");
    }
    // auth token
    const token = useUserStore().getToken;
    if (isEmpty(token)) {
      // 未登录状态则直接关闭连接
      this.close();
      return;
    }
    this.changeStatus(SocketStatus.CONNECTING);
    // 初始化SocketIO实例
    const configStorage = sessionStorage.getItem("adminConfig");
    let ioPath = import.meta.env.VITE_APP_SOCKET_URL || "wss://gojb.hackyous.com/";
    if (configStorage) {
      try {
        const config = JSON.parse(configStorage);
        ioPath = config.Ws;
      } catch (error) {
        console.log(error);
      }
    }
    this.socketInstance = IO(ioPath, {
      path: "/socket.io",
      transports: ["websocket"]
    });

		// connectok
    this.socketInstance.off(SocketIOWrapper.staticEvents[0]);
    this.socketInstance.on(SocketIOWrapper.staticEvents[0], this.handleConnectEvent.bind(this));
    this.socketInstance.off(SocketIOWrapper.staticEvents[1]);
    this.socketInstance.on(SocketIOWrapper.staticEvents[1], this.handleErrorEvent.bind(this));
    this.socketInstance.off(SocketIOWrapper.staticEvents[2]);
    this.socketInstance.on(SocketIOWrapper.staticEvents[2], this.handleDisconnectEvent.bind(this));
    // reconnecting
    this.socketInstance.off(SocketIOWrapper.staticEvents[8]);
    this.socketInstance.io.on(
      SocketIOWrapper.staticEvents[8],
      this.handleReconnectAttemptEvent.bind(this)
    );
  }

  // 订阅
  subscribe(eventName: any, fn: (data?: SocketMsg | KefuMsg) => void) {
    if (isEmpty(eventName) || !isFunction(fn)) {
      throw new TypeError("param必须更正类型");
    }
    this.socketInstance?.off(eventName);
    // register
    this.socketInstance?.on(eventName, fn);
  }

  // 取消订阅
  unsubscribe(eventName: any, fn: any) {
    if (isEmpty(eventName)) {
      throw new TypeError("param必须更正类型");
    }
    if (SocketIOWrapper.staticEvents.includes(eventName) && !isFunction(fn)) {
      throw new Error("默认事件取消订阅必须有第二个参数");
    }
    // off
    this.socketInstance?.off(eventName, fn);
  }

  // 派发事件通知Socket状态
  changeStatus(status: SocketStatusType) {
    this.socketStore.setStatus(status);
  }

  // 链接成功事件处理
  handleConnectEvent() {
    console.log("ws链接成功");
    this.changeStatus(SocketStatus.CONNECTED);
    // flush queue
    if (this.emitQueue.length > 0) {
      // copy
      const queue = this.emitQueue.slice();
      // clean
      this.emitQueue = [];
      for (let i = 0; i < queue.length; i++) {
        this.queueEmit(queue[i]);
      }
    }
    this.ping();
    if (this.storeName == "room") {
      this.joinroom();
    }
    if (this.storeName == "kefu") {
      this.joinKefuRoom();
    }
  }

  // 重连事件处理
  handleReconnectAttemptEvent() {
    console.log("ws重连");
    this.changeStatus(SocketStatus.CONNECTING);
  }

  // 关闭链接事件处理
  handleDisconnectEvent(reason: string) {
    console.log("ws断开连接");
    if (reason === "io server disconnect") {
      this.closeByServer = true;
      this.changeStatus(SocketStatus.CLOSE);
      this.close();
    }
  }

  // 错误事件处理
  handleErrorEvent() {
    if (this.closeByServer) {
      this.changeStatus(SocketStatus.CLOSE);
    }
  }

  /**
   * client emit
   * The following events are reserved and should not be used as event names by your application:
   * connect、connect_error、connect_timeout、error、disconnect、
   * disconnecting、newListener、reconnect_attempt、reconnecting、reconnect_error、
   * reconnect_failed、removeListener, ping, pong
   */
  emit(eventName: any, data: any) {
    // 检查event名称
    if (isEmpty(eventName) || SocketIOWrapper.staticEvents.includes(eventName)) {
      throw new TypeError("事件不允许发送");
    }
    if (!this.isConnected()) {
      // 未连接状态，则缓存，在重新连接时则会执行该队列
      this.emitQueue.push({ eventName, data });
    } else {
      // 连接成功状态
      this.socketInstance?.emit(eventName, data);
    }
  }

  // 重置队列标志状态
  resetState() {
    this.handleIndex = 0;
    this.runningQueue = [];
    this.waiting = this.flushing = false;
  }
  // 队列发送
  queueEmit(item: any) {
    if (!this.flushing) {
      this.runningQueue.push(item);
    } else {
      // if flushing
      let i = this.runningQueue.length - 1;
      while (i > this.handleIndex) {
        i--;
      }
      this.runningQueue.splice(i + 1, 0, item);
    }
    // queue the flush
    if (!this.waiting) {
      this.waiting = true;
      setTimeout(this.flushQueue.bind(this), 0);
    }
  }
  // flush queue
  flushQueue() {
    this.flushing = true;
    let item;
    // emit
    for (this.handleIndex = 0; this.handleIndex < this.runningQueue.length; this.handleIndex++) {
      item = this.runningQueue[this.handleIndex];
      // re emit
      this.emit(item.eventName, item.data);
    }

    this.resetState();
  }

  ping() {
    // ping
    this.socketInstance?.emit("ping");
    setInterval(() => {
      this.socketInstance?.emit("ping");
    }, 1000 * 15);
  }
  joinroom() {
    const userId = useUserStore().userInfo?.Id;
    const nickName = useUserStore().userInfo?.Name;
    const token = useUserStore().getToken;
    const roomId = useUserStore().userInfo?.RoomId;
    if (userId && token && roomId) {
      this.socketInstance?.emit("joinroom", {
        roomName: roomId+"",
        userId: `${userId}`,
        nickName: nickName,
        sendType: "PUBLIC"
      });
      $bus.emit("init-socket");
    }
  }

  joinKefuRoom() {
    const userId = useUserStore().userInfo?.Id;
    const nickName = useUserStore().userInfo?.Name;
    const token = useUserStore().getToken;
    const roomId = useUserStore().userInfo?.RoomId;
    if (userId && token && roomId) {
      this.socketInstance?.emit("joinroom", {
        roomName: "r_" + roomId,
        userId: userId,
        nickName: nickName,
        sendType: "private"
      });
    }
  }

  disconnect() {
    this.socketInstance?.disconnect();
  }
}
